#include "stdafx.h"

#include <MyControls/Bezier.h>
#include <glib/g2/algorithm.h>

namespace My { 

// BezierNode

BezierNode::BezierNode() :
	position(0, 0),
	type(bnFlat)
{
	control[0] = control[1] = point<float>(0, 0);
}

bool BezierNode::operator == (const BezierNode & n) const
{
	return position == n.position &&
		control[0] == n.control[0] &&
		control[1] == n.control[1] &&
		type == n.type;
}

bool BezierNode::operator != (const BezierNode & n) const
{
	return !operator ==(n);
}

void SetNodeControlPoint(BezierNode & n, Size c, point<float> p)
{
	Size c0 = c, c1 = 1 - c;
	switch (n.type) {
		case bnSymmetrical:
			n.control[c0] = p;
			n.control[c1] = 2 * n.position - p;
			break;
		case bnSmooth: { 
			n.control[c0] = p;
			float s0 = (float) abs(p - n.position);
			if (s0 != 0) {
				float s = (float) abs(n.control[c1] - n.position) / s0;
				n.control[c1] = n.position + (n.position - p) * s;
			}
			break;
		}
		case bnCusp:
			n.control[c0] = p;
			break;
		case bnFlat:
			assert(false);
	}
}

void SetNodeType(BezierNode & n, BezierNodeType t)
{
	if (t >= n.type) {
		n.type = t;
		return;
	}
	n.type = t;
	switch (n.type) {
		case bnFlat:
			n.control[0] = n.control[1] = n.position;
			break;
		case bnSymmetrical: {
			point<float> d = n.control[1] - n.control[0];
			n.control[0] = n.position - d / 2;
			n.control[1] = n.position + d / 2;
			break;
		}
		case bnSmooth: {
			point<float> d = normalize(n.control[1] - n.control[0]);
			n.control[0] = n.position + d * scalar_product(d, n.control[0] - n.position);
			n.control[1] = n.position + d * scalar_product(d, n.control[1] - n.position);
			break;
		}
	}
}

// Splits by 0.5
void SplitBezier2(
	const point<float> & p0,
	point<float> & p1,
	point<float> & p2,
	const point<float> & p3,
	point<float> & c0,
	point<float> & p,
	point<float> & c1)
{
	p = (p0 + p1 * 3 + p2 * 3 + p3) / 8;
	point<float> d = 
		(p3 - p2 * 3 + p1 * 3 - p0) / 4 +
		(p2 - p1 * 2 + p0) +
		(p1 - p0);
	c0 = p - d / 2;
	c1 = p + d / 2;
	p1 = p0 + (p1 - p0) / 2;
	p2 = p3 + (p2 - p3) / 2;
}

class BezierNearestSearcher {
public:
	BezierNearestSearcher(point<float> q, float a, float t) :
		accuracy(a), min(t), query(q)
	{ 
		lookup = rect<float>(q, q);
		lookup.inflate(t, t);
	}
public:
	void Search(
		point<float> p0, 
		point<float> p1, 
		point<float> p2, 
		point<float> p3,
		float t0, float t1)
	{
		rect<float> range(p0, p0);
		range.include2(p1);
		range.include2(p2);
		range.include2(p3);
		if (!intersect(range, lookup))
			return;
		if (range.width() < accuracy && range.height() < accuracy) {
			// small enough? treat as a segment(p0, p3):
			g2::vectorf v(query.x, query.y), v0(p0.x, p0.y), v1(p1.x, p1.y);
			float s, d = g2::distance<float>(v, v0, v1, 0, &s); 
			if (d < min) {
				min = d;
				result = (1 - s) * t0 + s * t1;
			}
		} else {
			point<float> c0, p, c1;
			SplitBezier2(p0, p1, p2, p3, c0, p, c1);
			float middle = (t0 + t1) / 2;
			Search(p0, p1, c0, p, t0, middle);
			Search(p, c1, p2, p3, middle, t1);
		}
	}
public:
	point<float>	query;
	rect<float>		lookup;
	float			accuracy;
	float			min;
	float			result;
};

bool BezierNearest(
	const point<float> & p0, 
	const point<float> & p1, 
	const point<float> & p2, 
	const point<float> & p3, 
	const point<float> & q,
	float accuracy,
	float threshold,
	float & d,
	float & t)
{
	BezierNearestSearcher s(q, accuracy, threshold);
	s.Search(p0, p1, p2, p3, 0.f, 1.f);
	d = s.min;
	t = s.result;
	return s.min < threshold;
}

point<float> BezierAt(
	const point<float> & p0, 
	const point<float> & p1, 
	const point<float> & p2, 
	const point<float> & p3, 
	float t)
{
	return p0 * (1 - t) * (1 - t) * (1 - t) +
		p1 * 3 * (1 - t) * (1 - t) * t +
		p2 * 3 * (1 - t) * t * t +
		p3 * t * t * t;
}

void SplitBezier(
	const point<float> & p0,
	point<float> & p1,
	point<float> & p2,
	const point<float> & p3,
	float t, 
	point<float> & c0,
	point<float> & p,
	point<float> & c1)
{
	p = BezierAt(p0, p1, p2, p3, t);
	point<float> d = (p3 - p2 * 3 + p1 * 3 - p0) * t * t +
		(p2 - p1 * 2 + p0) * 2 * t +
		(p1 - p0);
	c0 = p - d * t;
	c1 = p + d * (1 - t);
	p1 = p0 + (p1 - p0) * t;
	p2 = p3 + (p2 - p3) * (1 - t);
}

} // My